package com.ghp.singleton;

import com.ghp.singleton.hungry.HungrySingleton1;
import com.ghp.singleton.hungry.HungrySingleton2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;

/**
 * @author 知识汲取者
 * @date 2025/7/15
 * @title
 * @description
 **/
public class Test {

    public static void main(String[] args) {
        Class<?> clazz = HungrySingleton1.class;
//        Class<?> clazz = LazySingleton3.class;
//        testThreadSafetyProblem(clazz);
//        testSerializableProblem(clazz);
        testReflectionProblem(clazz);

//        testEnumSingletonProblem();
    }

    /**
     * 测试枚举单例模式是否存在线程安全问题、反序列化问题、反射问题
     * 枚举类无需实现 Serializable 接口，他的父类 {@link java.lang.Enum} 默认实现 Serializable 接口
     * 枚举类默认会生成一个私有的的构造器，但是无法通过反射获取，因为它添加了两个隐式参数，具体参考： {@link Enum#Enum(String, int)}
     * 通过反射获取枚举对象会抛出异常，具体参考：{@link Constructor#newInstance(Object...)}
     */
    private static void testEnumSingletonProblem(){
        // 测试枚举单例是否存在线程安全问题
        int concurrentNumber = 100;
        Set<Integer> set = Collections.newSetFromMap(new ConcurrentHashMap<>());
        CountDownLatch latch = new CountDownLatch(concurrentNumber);
        for (int i = 0; i < concurrentNumber; i++) {
            new Thread(() -> {
                set.add(HungrySingleton2.INSTANCE.hashCode());
                latch.countDown();
            }).start();
        }
        try {
            latch.await();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (set.size() > 1) {
            System.out.println("单例模式存在线程安全问题！");
        }

        // 测试枚举单例是否存在序列化问题
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            Object obj1 = HungrySingleton2.INSTANCE;
            oos = new ObjectOutputStream(new FileOutputStream("tempFile.txt"));
            oos.writeObject(obj1);
            ois = new ObjectInputStream(new FileInputStream("tempFile.txt"));
            Object obj2 = ois.readObject();
            if (obj1.hashCode() != obj2.hashCode()) {
                System.out.println("单例模式存在序列化问题！");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if (ois != null){
                    ois.close();
                }
                if (oos != null){
                    oos.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // 测试枚举单例是否存在反射问题
        try {
            Class<?> clazz = HungrySingleton2.class;
            // 枚举类无法通过反射获取无参构造器
            Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);
            // 枚举类通过反射创建对象会报错
            Object obj1 = constructor.newInstance("1", 1);
            Object obj2 = constructor.newInstance("1", 1);
            System.out.println(obj1.hashCode() == obj2.hashCode());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 测试单例模式是否存在的线程安全问题
     *
     * @param clazz 单例类
     */
    private static void testThreadSafetyProblem(Class<?> clazz) {
        int concurrentNumber = 100;
        Set<Integer> set = Collections.newSetFromMap(new ConcurrentHashMap<>());
        CountDownLatch latch = new CountDownLatch(concurrentNumber);

        for (int i = 0; i < concurrentNumber; i++) {
            new Thread(() -> {
                Object obj = getInstance(clazz);
                set.add(obj.hashCode());
                latch.countDown();
            }).start();
        }

        try {
            latch.await();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (set.size() > 1) {
            System.out.println("单例模式存在线程安全问题！");
        }
    }

    /**
     * 测试单例模式是否存在序列化问题
     *
     * @param clazz 单例类
     */
    private static void testSerializableProblem(Class<?> clazz) {
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            // 将单例对象进行序列化
            Object obj1 = getInstance(clazz);
            oos = new ObjectOutputStream(new FileOutputStream("tempFile.txt"));
            oos.writeObject(obj1);
            // 将单例对象进行反序列化
            ois = new ObjectInputStream(new FileInputStream("tempFile.txt"));
            Object obj2 = ois.readObject();
            // 判断序列化前后的对象是否发生了变化
            if (obj1.hashCode() != obj2.hashCode()) {
                System.out.println("单例模式存在序列化问题！");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if (ois != null){
                    ois.close();
                }
                if (oos != null){
                    oos.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 测试单例模式是否存在反射问题
     *
     * @param clazz 单例类
     */
    private static void testReflectionProblem(Class<?> clazz) {
        try {
            Object obj1 = null;
            Object obj2 = null;
            // 方式一：没有初始化单例对象直接多次反射创建单例对象
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            obj1 = constructor.newInstance();
            obj2 = constructor.newInstance();
            if (obj1.hashCode() != obj2.hashCode()){
                System.out.println("方式一：单例模式存在反射问题！");
            }

            // 方式二：先调用 get 方法初始化单例对象，再进行反射创建对象
            obj1 = getInstance(clazz);
            obj2 = constructor.newInstance();
            if (obj1.hashCode() != obj2.hashCode()){
                System.out.println("方式二：单例模式存在反射问题！");
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 获取单例实例
     *
     * @param clazz
     * @return
     */
    private static Object getInstance(Class<?> clazz) {
        try {
            return clazz.getMethod("getInstance").invoke(null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
